#include "qshell.h"
#include "qshellprivate.h"
#include <QCoreApplication>

AbstractQConsoleListener::AbstractQConsoleListener() {
  QObject::connect(this, &AbstractQConsoleListener::finishedGetLine, this,
                   &AbstractQConsoleListener::on_finishedGetLine,
                   Qt::QueuedConnection);
#ifdef Q_OS_WIN
  m_notifier = new QWinEventNotifier(GetStdHandle(STD_INPUT_HANDLE));
#else
  m_notifier = new QSocketNotifier(fileno(stdin), QSocketNotifier::Read);
#endif
  // NOTE : move to thread because std::getline blocks,
  //        then we sync with main thread using a QueuedConnection with
  //        finishedGetLine
  m_notifier->moveToThread(&m_thread);
  QObject::connect(&m_thread, &QThread::finished, m_notifier,
                   &QObject::deleteLater);
#ifdef Q_OS_WIN
  QObject::connect(m_notifier, &QWinEventNotifier::activated,
#else
  QObject::connect(m_notifier, &QSocketNotifier::activated,
#endif
                   [this]() {
                     std::string line;
                     std::getline(std::cin, line);
                     QString strLine = QString::fromStdString(line);
                     Q_EMIT this->finishedGetLine(strLine);
                   });
  m_thread.start();
}

void AbstractQConsoleListener::on_finishedGetLine(const QString &strNewLine) {
  parse_newline(strNewLine);
  if (strNewLine == "quit") {
    QMetaObject::invokeMethod(QCoreApplication::instance(), "quit",
                              Qt::QueuedConnection);
    return;
  }
}

AbstractQConsoleListener::~AbstractQConsoleListener() {
  m_thread.quit();
  m_thread.wait();
}

#include <QLogger>
#include <QTextStream>
constexpr int HEADER_SIDE_LENGTH = 10;
QShellPrivate::QShellPrivate(const QString &name, QShell *msh)
    : AbstractQConsoleListener(), sh(msh) {
  QString header = QString(HEADER_SIDE_LENGTH * 2 + name.size(), '-');
  QString title = QString('|') + QString(HEADER_SIDE_LENGTH - 1, ' ') + name +
                  QString(HEADER_SIDE_LENGTH - 1, ' ') + QString('|');
  QString border = QString(header.size(), ' ');

  QTextStream out(stdout);

  out << header << Qt::endl
      << title << Qt::endl
      << header << Qt::endl
      << border << Qt::endl
      << ">>";
  Qt::flush(out);
}
QShellPrivate::~QShellPrivate() {}
QCmdParser &QShellPrivate::addSubCommand(const QString &cmd) {
  Q_ASSERT(mSubCmds.size() == 0 || mSubCmds.find(cmd) == mSubCmds.end());
  mSubCmds.insert(cmd, QSharedPointer<QCmdParser>::create());
  return *mSubCmds[cmd];
}
void QShellPrivate::parse(int argc, char *argv[]) {
  sh->mCmdParser.parse_check(argc, argv);
}
void QShellPrivate::parse_newline(const QString &str) {
  auto sl = str.split(QLatin1Char(' '));
  QString subcmd = sl[0];

  if (str.isEmpty()) {
    QLogger::Warn("Can\'t input empty");
  } else if (sl.size() < 1 || !mSubCmds.contains(subcmd)) {
    QLogger::Error(QString("Can\'t find command: ") + sl[0]);
  } else if (!mSubCmds[subcmd]->parse(str.toStdString())) {
    QLogger::Error(QString::fromStdString(mSubCmds[sl[0]]->error_full()));
  } else {
    (*mSubCmds[subcmd])();
  }

  std::cout << ">>" << std::flush;
}

QShell::QShell(const QString &name)
    : QObject(), d_ptr(new QShellPrivate(name, this)) {}
QShell::~QShell() {}
void QShell::parse(int argc, char *argv[]) { d_ptr->parse(argc, argv); }

QCmdParser &QShell::addSubCommand(const QString &cmd) {
  return d_ptr->addSubCommand(cmd);
}
